﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Data.Objects;
using System.Data;
using System.Data.Metadata.Edm;
using System.Data.Objects.DataClasses;
using UtfprLibrary.EntityFramework.Exceptions;

namespace UtfprLibrary.EntityFramework.Helpers {
    /// <summary>
    /// TODO
    /// </summary>
    public static class ObjectContextHelper {
        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="dataSource"></param>
        /// <returns></returns>
        public static Type GetEntityType(object dataSource) {
            try {
                return (Type)dataSource.GetType().GetProperty("UnderlyingSystemType").GetValue(dataSource, null);
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="entityType"></param>
        /// <returns></returns>
        public static Type GetContextType(Type entityType) {
            try {
                foreach (Type type in entityType.Assembly.GetTypes()) {
                    if (type.Namespace == entityType.Namespace) {
                        if (type.IsSubclassOf(typeof(ObjectContext))) {
                            var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly;
                            PropertyInfo[] props = type.GetProperties(flags);

                            foreach (var p in props) {
                                if (p.PropertyType.GetGenericArguments()[0] == entityType) {
                                    return type;
                                }
                            }
                        }
                    }
                }
                throw new ArgumentException("Context Type not found.");
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="entity"></param>
        /// <returns></returns>
        public static EntityState GetEntityState(object entity) {
            try {
                var stateInfo = entity.GetType().GetProperty("EntityState");
                return (EntityState)stateInfo.GetValue(entity, null);
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="state"></param>
        /// <param name="entity"></param>
        public static void SetEntityState(ObjectContext ctx, EntityState state, object entity) {
            try {
                ctx.ObjectStateManager.ChangeObjectState(entity, state);
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <returns></returns>
        public static string GetIdName(ObjectContext ctx, Type entityType) {
            try {
                MetadataWorkspace workspace = ctx.MetadataWorkspace;
                workspace.LoadFromAssembly(Assembly.Load(entityType.Assembly.GetName().Name));

                EdmType edmOS = workspace.GetItems<EdmType>(DataSpace.OSpace)
                    .Where(type => type.FullName.Equals(entityType.FullName))
                    .FirstOrDefault();

                EdmMember edmMemberID = ((ReadOnlyMetadataCollection<EdmMember>)edmOS.GetType()
                    .BaseType.BaseType.GetProperty("KeyMembers").GetValue(edmOS, null)).FirstOrDefault();

                return edmMemberID.Name;
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="entityType"></param>
        /// <returns></returns>
        public static string GetEntitySetFullName(ObjectContext ctx, Type entityType) {
            try {
                return ctx.GetEntitySetFullName(entityType.Name);
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static object InvokeCreateQuery(ObjectContext ctx, Type entityType, object[] parameters) {
            try {
                return ctx.GetType().GetMethod("CreateQuery")
                    .MakeGenericMethod(new Type[] { entityType })
                    .Invoke(ctx, parameters);
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="objectQuery"></param>
        /// <param name="path"></param>
        /// <returns></returns>
        public static object InvokeInclude(object objectQuery, string path) {
            try {
                return objectQuery.GetType().GetMethod("Include")
                    .Invoke(objectQuery, new Object[] { path });
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="objectQuery"></param>
        /// <param name="entityType"></param>
        /// <returns></returns>
        public static object InvokeFirstOrDefault(object objectQuery, Type entityType) {
            try {
                var flags = BindingFlags.Static | BindingFlags.Public;

                var firstOrDefaultMethods = typeof(System.Linq.Queryable)
                    .GetMethods(flags).Where(mi => mi.Name == "FirstOrDefault");

                MethodInfo firstOrDefault = null;

                foreach (var methodInfo in firstOrDefaultMethods) {
                    if (methodInfo.GetParameters().Count() == 1) {
                        firstOrDefault = methodInfo;
                        break;
                    }
                }

                return firstOrDefault.MakeGenericMethod(new Type[] { entityType })
                    .Invoke(objectQuery, new object[] { objectQuery });
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <returns></returns>
        public static string CreateESQLQueryById(ObjectContext ctx, Type entityType, bool isString, bool isCaseInsensitive) {
            try {
                if (isString && isCaseInsensitive) {
                    return @"SELECT VALUE val FROM " + GetEntitySetFullName(ctx, entityType) +
                            " AS val WHERE ToUpper(val." + GetIdName(ctx, entityType) + ") = ToUpper(@id)";
                } else {
                    return @"SELECT VALUE val FROM " + GetEntitySetFullName(ctx, entityType) +
                        " AS val WHERE val." + GetIdName(ctx, entityType) + "=@id";
                }
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        public static string CreateESQLQueryById(ObjectContext ctx, Type entityType) {
            try {
                return @"SELECT VALUE val FROM " + GetEntitySetFullName(ctx, entityType) +
                    " AS val WHERE val." + GetIdName(ctx, entityType) + "=@id";
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="id"></param>
        /// <param name="entityType"></param>
        /// <returns></returns>
        public static object[] GetParameters(ObjectContext ctx, object id, bool isString, bool isCaseInsensitive, Type entityType) {
            try {
                object idValue = id;
                if (isString) {
                    idValue = id.ToString();
                    if (isCaseInsensitive) {
                        idValue = id.ToString().ToUpper();
                    }
                }

                return new object[] { 
                CreateESQLQueryById(ctx, entityType, isString, isCaseInsensitive), 
                new ObjectParameter[] { 
                    new ObjectParameter("id", idValue) } };
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        public static object[] GetParameters(ObjectContext ctx, object id, Type entityType) {
            try {
                return new object[] { 
                CreateESQLQueryById(ctx, entityType), 
                new ObjectParameter[] { 
                    new ObjectParameter("id", id) } };
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }

        /// <summary>
        /// TODO
        /// </summary>
        /// <param name="property"></param>
        /// <returns></returns>
        public static bool IsRelatedEnd(PropertyInfo property) {
            try {
                var propertyType = (Type)property.GetType()
                    .GetProperty("PropertyType").GetValue(property, null);

                //if (propertyType.BaseType == typeof(RelatedEnd) || propertyType.BaseType == typeof(EntityObject)) {
                if (propertyType.BaseType == typeof(RelatedEnd) || propertyType.BaseType == typeof(EntityReference)) {
                    return true;
                } else {
                    return false;
                }
            } catch (Exception ex) {
                throw new UtfprEFHelpersException("", ex);
            }
        }
    }
}
